#include "xmppcore/xmppparsingfactory.h"
#include "xmppcore/constants.h"
#include "xmppcore/stanza.h"
#include "xmppcore/stanzaerror.h"
#include "xmppcore/stanzaerrorparser.h"
#include "xmppcore/parsingutils.h"

#include <QtCore/QXmlStreamReader>

namespace yoohoo_xmppcore {

const QString XmppParsingFactory::NAMESPACE_STREAM = "http://etherx.jabber.org/streams";

XmppParsingFactory::XmppParsingFactory() : _debug(false) {
}

XmppParsingFactory::~XmppParsingFactory() {
	foreach (XmppParserFactory* parserFactory, _parserFactories.values()) {
		delete parserFactory;
	}
}

void XmppParsingFactory::setDebug(bool debug) {
	_debug = debug;
}

bool XmppParsingFactory::debug() {
	return _debug;
}

XmppElement* XmppParsingFactory::parse(QString xml, bool stream) {
	QReadLocker locker(&_lock);
	
	QString amendedXml;
	amendedXml.append("<stream:stream xmlns:stream='http://etherx.jabber.org/streams'>");
	amendedXml.append(xml);
	amendedXml.append("</stream:stream>");

	QXmlStreamReader reader(amendedXml);
	reader.readNext(); // start document

	reader.readNext(); // stream tag
	QXmlStreamReader::TokenType type = reader.readNext();
	if (type != QXmlStreamReader::StartElement) {
		return 0;
	}

	XmppObject* obj = parseProtocol(&reader, xml, ProtocolKey::NULL_PROTOCOL_KEY);
	if (!obj) {
		return 0;
	}

	XmppElement* element = dynamic_cast<XmppElement*>(obj);
	if (!element) {
		delete obj;
	}

	return element;
}

XmppObject* XmppParsingFactory::parseProtocol(QXmlStreamReader* reader, QString& xml, ProtocolKey enclosing) {
	QString localName = reader->name().toString();
	QString namespaceUri = reader->namespaceUri().toString();

	XmppParserFactory* parserFactory = findParserFactory(localName, namespaceUri);
	if (!parserFactory)
		return 0;

	ProtocolRelation relation(enclosing, parserFactory->protocolKey());
	if (!_protocolRelations.contains(relation)) {
		return 0;
	}

	ParsingHierarchy hierarchy(localName);
	
	XmppParser* parser = parserFactory->createParser();
	if (parser == 0) {
		return 0;
	}

	if (!readStanzaAttributes(hierarchy, reader, parser)) {
		cleanParsingEnv(parser);
		return 0;
	}

	// stanza errors are so special. they need to be processed separately.
	if (enclosing.isNull()) {
		XmppObject* obj = parser->obj();
		Stanza* stanza = dynamic_cast<Stanza*>(obj);
		if (stanza) {
			StanzaError* error = dynamic_cast<StanzaError*>(stanza);
			if (!error && XMPP_STRING_ERROR == stanza->type()) {
				StanzaErrorParser stanzaErrorParser(this, reader, xml, stanza);
				error = stanzaErrorParser.parse();
				cleanParsingEnv(parser);

				return (XmppObject*)error;
			}
		}
	}

	bool ok = parseStanza(reader, xml, localName, namespaceUri, hierarchy, parser);
	if (ok) {
		XmppObject* obj = parser->obj();
		delete parser;

		return obj;
	} else {
		cleanParsingEnv(parser);
		return 0;
	}
}

void XmppParsingFactory::cleanParsingEnv(XmppParser* parser) {
	XmppObject* obj = parser->obj();
	if (obj) {
		delete obj;
	}

	delete parser;
}

bool XmppParsingFactory::parseStanza(QXmlStreamReader* reader, QString& xml, QString& localName, QString& namespaceUri,
		ParsingHierarchy& hierarchy, XmppParser* parser) {
	QString text;
	while (true) {
		QXmlStreamReader::TokenType type = reader->readNext();
		if (type == QXmlStreamReader::Characters) {
			ParsingUtils::appendText(reader, text);
		} else if (type == QXmlStreamReader::EndElement) {
			if (_debug) {
				qDebug("Parsing text: %s", qPrintable(hierarchy.toString()));
			}

			if (!parser->processText(hierarchy.toString(), text)) {
				return false;
			}

			break;
		} else if (type == QXmlStreamReader::StartElement) {
			if (!parseChildElement(reader, xml, localName, namespaceUri, &hierarchy, parser)) {
				return false;
			}
		} else {
			return false;
		}
	}

	return true;
}

bool XmppParsingFactory::readStanzaAttributes(ParsingHierarchy& hierarchy, QXmlStreamReader* reader, XmppParser* parser) {
	if (_debug) {
		qDebug("Parsing attributes: %s", qPrintable(hierarchy.toString()));
	}

	return parser->processAttributes(hierarchy.toString(), readAttributes(reader));
}

XmppParserFactory* XmppParsingFactory::findParserFactory(QString& localName, QString& namespaceUri) {
	ProtocolKey fullNameProtocolKey(localName, namespaceUri);
	if (_parserFactories.contains(fullNameProtocolKey)) {
		return _parserFactories[fullNameProtocolKey];
	}

	ProtocolKey namespaceOnlyProtocolKey(namespaceUri);
	if (_parserFactories.contains(namespaceOnlyProtocolKey)) {
		return _parserFactories[namespaceOnlyProtocolKey];
	}

	return 0;
}

bool XmppParsingFactory::parseChildElement(QXmlStreamReader* reader, QString& xml, const QString& name, const QString& namespaceUri,
		ParsingHierarchy* hierarchy, XmppParser* parser) {
	QString childElementLocalName = reader->name().toString();
	QString childElementNamespace = reader->namespaceUri().toString();

	if (namespaceUri != childElementNamespace) {
		ProtocolKey enclosingPk(name, namespaceUri);
		ProtocolKey embeddedPk(childElementLocalName, childElementNamespace);
		XmppObject* embeddedObj = parseProtocol(reader, xml, enclosingPk);
		if (embeddedObj) {
			if (_debug) {
				qDebug("Parsing embedded object: %s", qPrintable(hierarchy->toString()));
			}

			return parser->processEmbeddedObject(embeddedPk, embeddedObj);
		} else {
			return false;
		}
	} else {
		ParsingHierarchy childHierarchy(childElementLocalName);
		childHierarchy.setParent(hierarchy);

		return parseElement(reader, xml, childHierarchy, namespaceUri, parser);
	}

	return true;
}

bool XmppParsingFactory::parseElement(QXmlStreamReader* reader, QString& xml, ParsingHierarchy& hierarchy,
		const QString& currentNamespace, XmppParser* parser) {
	if (_debug) {
		qDebug("Parsing attributes: %s", qPrintable(hierarchy.toString()));
	}

	if (!parser->processAttributes(hierarchy.toString(), readAttributes(reader)))
		return false;
		
	QString text;
	while (true) {
		QXmlStreamReader::TokenType type = reader->readNext();
		if (type == QXmlStreamReader::Characters) {
			ParsingUtils::appendText(reader, text);
		} else if (type == QXmlStreamReader::EndElement) {
			if (_debug) {
				qDebug("Parsing text: %s", qPrintable(hierarchy.toString()));
			}

			if (!parser->processText(hierarchy.toString(), text))
				return false;

			return true;
		} else if (type == QXmlStreamReader::StartElement) {
			if (!parseChildElement(reader, xml, hierarchy.localName(), currentNamespace, &hierarchy, parser))
				return false;
		} else {
			return false;
		}
	}
}

QList<ParsingAttribute> XmppParsingFactory::readAttributes(QXmlStreamReader* reader) {
	QList<ParsingAttribute> attributes;

	QVectorIterator<QXmlStreamAttribute> iter(reader->attributes());
	while (iter.hasNext()) {
		QXmlStreamAttribute attribute = iter.next();
		attributes.append(ParsingAttribute(attribute.prefix().toString(),
			attribute.name().toString(), attribute.value().toString()));
	}
	
	return attributes;
}

void XmppParsingFactory::registerParser(XmppParserFactory* parserFactory, ProtocolRelation relation) {
	ProtocolRelations relations;
	relations.addProtocolRelation(relation);

	registerParser(parserFactory, relations);
}

void XmppParsingFactory::registerParser(XmppParserFactory* parserFactory, ProtocolRelations relations) {
	QWriteLocker locker(&_lock);

	if (parserFactory == 0)
		return;

	_parserFactories.insert(parserFactory->protocolKey(), parserFactory);
	if (relations.protocolRelations().size() > 0) {
		_protocolKeyAndRelations.insert(parserFactory->protocolKey(), relations);
		foreach (ProtocolRelation relation, relations.protocolRelations()) {
			_protocolRelations.insert(relation);
		}
	}
}

void XmppParsingFactory::unregisterParser(XmppParserFactory* parserFactory) {
	QWriteLocker locker(&_lock);

	if (parserFactory == 0)
		return;

	ProtocolRelations relations = _protocolKeyAndRelations[parserFactory->protocolKey()];
	foreach (ProtocolRelation relation, relations.protocolRelations()) {
		_protocolRelations.remove(relation);
	}
	_protocolKeyAndRelations.remove(parserFactory->protocolKey());
	_parserFactories.remove(parserFactory->protocolKey());
}

XmppParsingFactory::ParsingHierarchy::ParsingHierarchy(QString localName, ParsingHierarchy* parent) :
		_localName(localName), _parent(parent) {
}

void XmppParsingFactory::ParsingHierarchy::setParent(ParsingHierarchy* parent) {
	_parent = parent;
	_changed = true;
}

const QString& XmppParsingFactory::ParsingHierarchy::localName() const {
	return _localName;
}

QString XmppParsingFactory::ParsingHierarchy::toString() {
	if (_changed || _path.isEmpty()) {
		_path.clear();
		if (_parent) {
			_path.append(_parent->toString());
			_path.append('/');
		}

		_path.append(_localName);
	}

	return _path;
}

}
